/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 *
 */

import { PackInfo } from "../options/configure/pack-info-options";
import PackageObj = PackInfo.PackageObj;
import { ValidateUtil } from "./validate/validate-util";
import fse from "fs-extra";
import { OhosLogger } from "./log/ohos-logger";
import { ohosTrace } from "../common/trace/ohos-trace";
import { getJson5ObjThrowError } from "./json-util";

const _log = OhosLogger.getLogger('pac-util');

export interface DataLabel {
    label: string;
    purposes: string[];
}

export interface DataProcess {
    dataType: string;
    dataLabels: DataLabel[];
}

export interface SpecialAPI {
    apiType: string;
    reasons: string[];
}

export interface DependencyInfo {
    author?: string;
    name: string;
    description?: string;
    version?: string;
}

export interface PacFile {
    dataProcess?: DataProcess[];
    specialAPIs?: SpecialAPI[];
    dependencies?: DependencyInfo[];
}

export interface PackageJsonWithOtherInfo extends PackageObj {
    author?: string;
    name: string;
    description?: string;
    version?: string;
}

/**
 * 检查pac.json是否包含必选字段, 当前规格为'dataProcess', 'specialAPIs'其中之一
 *
 * @param filePath
 */
export function checkPacJsonRequiredFields(filePath: string): boolean {
    if (!fse.existsSync(filePath)) {
        return false;
    }
    let pacJson = {};
    try {
        pacJson = getJson5ObjThrowError(filePath) ?? {};
    } catch (e) {
        _log.debug('Catch error when parse pac.json5.', e);
    }

    // 检查必选字段
    const checkItem = ['dataProcess', 'specialAPIs'];
    const hasRequiredField = checkItem.some((item) =>
        Object.prototype.hasOwnProperty.call(pacJson, item)
    );

    if (!hasRequiredField) {
        _log.warn(`'pac.json5' is not properly enabled. Please check if the file ${filePath} contains the required fields: 'dataProcess' or 'specialAPIs'.`);
    }
    return hasRequiredField;
}

type SpecialAPIMap = Map<string, Set<string>>;
type PurposesSet = Set<string>;
type LabelToPurposeMap = Map<string, PurposesSet>;

function processDataProcesses(dataProcess: DataProcess[], dataTypeToDataLabelMap: Map<string, LabelToPurposeMap>) {
    const processDataTypeExist = (dataProcess: DataProcess) => {
        const labelToPurposeMap = dataTypeToDataLabelMap.get(dataProcess.dataType)!;
        processDataLabelsToMap(dataProcess.dataLabels, labelToPurposeMap);
    }

    const addDataProcessToMap = (dataProcess: DataProcess) => {
        const labelToPurposeMap = new Map<string, PurposesSet>();
        processDataLabelsToMap(dataProcess.dataLabels, labelToPurposeMap);
        dataTypeToDataLabelMap.set(dataProcess.dataType, labelToPurposeMap);
    }

    function processDataLabelsToMap(dataLabels: DataLabel[], labelToPurposeMap: Map<string, PurposesSet>) {
        dataLabels.forEach(dataLabel => {
            if (labelToPurposeMap.has(dataLabel.label)) {
                labelToPurposeMap.set(dataLabel.label, new Set([...Array.from(labelToPurposeMap.get(dataLabel.label) ?? []), ...(dataLabel.purposes ?? [])]));
            } else {
                labelToPurposeMap.set(dataLabel.label, new Set(dataLabel.purposes));
            }
        });
    }
    // 合并 dataProcess
    dataProcess?.forEach((dataProcess) => {
        if (dataTypeToDataLabelMap.has(dataProcess.dataType)) {
            processDataTypeExist(dataProcess);
        } else {
            addDataProcessToMap(dataProcess);
        }
    });

    return dataTypeToDataLabelMap;
}

function processSpecialAPIs(specialAPIs: SpecialAPI[], specialAPIMap: SpecialAPIMap) {
    specialAPIs?.forEach((specialAPI) => {
        if (specialAPIMap.has(specialAPI.apiType)) {
            specialAPI.reasons.forEach(reason => specialAPIMap.get(specialAPI.apiType)!.add(reason));
        } else {
            specialAPIMap.set(specialAPI.apiType, new Set(specialAPI.reasons));
        }
    });
    return specialAPIMap;
}


/**
 * 合并多个Pac文件
 * @param pacObjs - 需要合并的Pac文件数组
 * @return 返回合并后的Pac文件
 */
export function mergePacFiles(pacObjs: PacFile[]): PacFile {
    // 初始化合并后的Pac文件
    const mergedPac: PacFile = {
        dataProcess: [],
        specialAPIs: [],
    };
    // 初始化dataTypeToDataLabelMap和specialAPIs的Map
    const dataTypeToDataLabelMap = new Map<string, LabelToPurposeMap>();

    const specialAPIMap = new Map() as SpecialAPIMap;

    // 遍历每个Pac文件
    pacObjs.forEach((pac) => {
        // 合并 dataProcess
        processDataProcesses(pac.dataProcess ?? [], dataTypeToDataLabelMap);
        // 合并 specialAPIs
        processSpecialAPIs(pac.specialAPIs ?? [], specialAPIMap);
    });

    // 遍历dataTypeToDataLabelMap，将其转换为数组并添加到合并后的Pac文件中
    for (const [dataType, dataLabels] of dataTypeToDataLabelMap.entries()) {
        const dataLabelArray = [];
        for (const [label, purposes] of dataLabels.entries()) {
            const dataLabelsEntry: DataLabel = {
                label: label,
                purposes: Array.from(purposes),
            }
            dataLabelArray.push(dataLabelsEntry);
        }
        const dataProcessEntry: DataProcess = {
            dataType: dataType,
            dataLabels: dataLabelArray,
        }
        mergedPac.dataProcess!.push(dataProcessEntry);
    }
    // 遍历特殊APIs Map，将其转换为数组并添加到合并后的Pac文件中
    for (const [apiType, reasons] of specialAPIMap.entries()) {
        const specialAPIEntry: SpecialAPI = {
            apiType: apiType,
            reasons: Array.from(reasons),
        }
        mergedPac.specialAPIs!.push(specialAPIEntry);
    }
    // 返回合并后的Pac文件
    return mergedPac;
}

/**
 * 合并依赖项
 * @param {PackageJsonWithOtherInfo[]} packageJsonList - 需要合并的oh-package.json5列表
 * @param {PacFile} mergedPacJsonObj - 已合并的oh-package.json5对象
 * @throws {Error} 如果mergedPacJsonObj不存在dependencies属性，会抛出错误
 * @return {void}
 */
export function mergeDependencies(packageJsonList: PackageJsonWithOtherInfo[], mergedPacJsonObj: PacFile): void {
    // 确保dependencies数组存在
    if (!mergedPacJsonObj.dependencies) {
        mergedPacJsonObj.dependencies = [];
    }

    // 构建现有依赖的唯一键集合，用于去重
    const existingKeys = new Set<string>();
    for (const packageJson of packageJsonList) {
        const dependency: DependencyInfo = {
            author: packageJson.author ?? '',
            name: packageJson.name,
            description: packageJson.description ?? '',
            version: packageJson.version ?? '',
        };
        const key = JSON.stringify({
            name: dependency.name,
            version: dependency.version,
        });

        // 跳过重复项
        if (existingKeys.has(key)) {
            continue;
        }
        mergedPacJsonObj.dependencies.push(dependency);
        existingKeys.add(key);
    }
}

export function tracePac(moduleName: string) {
    ohosTrace.traceIsUsePacJson(moduleName);
}

export function checkPacJsonExistAndGetObj(pacPath: string): PacFile | undefined {
    if (!fse.existsSync(pacPath)) {
        return undefined;
    }
    try {
        return getJson5ObjThrowError(pacPath) ?? {};
    } catch (e) {
        _log.debug('Catch error when parse pac.json5.', e);
    }
    return undefined;
}

export function pacFileValidate(moduleName: string, originalPacJsonPath:string, pacSchemaPath: string) {
    if (!fse.existsSync(originalPacJsonPath) || !fse.existsSync(pacSchemaPath)) {
        return false;
    }
    if (!checkPacJsonRequiredFields(originalPacJsonPath)) {
        return false;
    }
    ValidateUtil.submitSchemaCheckWork({
        moduleName: moduleName,
        filePath: originalPacJsonPath,
        schemaPath: pacSchemaPath,
    });
    return true;
}